package cn.edu.zut.hhr.utils;

import cn.edu.zut.hhr.entity.Article;
import cn.edu.zut.hhr.service.ArticleService;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

@Component
public class StatisticsUtil{
    /**
     * 用于同步redis中数据到数据库
     *
     * 把这个set(名字为"recodeset_"+i,i用volicat修饰,防止指令重排序)存到redis中(让redis自动帮我们保证线程安全,
     * 这个存储即使指令重排序了也没关系),设置过期时间为2h5min,定时器2h进行一次同步,
     * 同步的同时让i自增一,创建一个新set(名字为recodeset_i)(往里面添加一个无用值),
     * 作为下次扫描的集合,因为这个即使线程是单线程进行同步的,所以用变量i不会出现任何问题
     *
     * 然后从redis中获取某个文章的浏览信息是为了防止改文章已过期的情况,
     * 每当一个文章被浏览/点赞了就需要在redis中重置他的过期时间为24h(指定个大于2h10min的值就行)
     *
     * 然后i++的时候如果有数据被记录可能会出现记录到上一个set中的情况,这里用读写锁来避免这种情况的发生
     */
    private static SimpleDateFormat sd = new SimpleDateFormat("yyyy_MM_dd");
    private static RedisUtil redisUtil;
    private static ArticleService articleService;
    private static Timer timer;
    private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static Lock readLock = readWriteLock.readLock();
    private static Lock writeLock = readWriteLock.writeLock();
    private static final String RECODE = "recode_";
    /**
     * volatile防止指令重排序让i++先执行
     */
    private volatile static int i = 0;
    static{
        timer = new Timer(true);
        //每2小时同步一次
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                Set<Long> set = new HashSet<>();
                try{
                    writeLock.lock();
                    if(redisUtil != null)
                        set = redisUtil.zGet(RECODE+i);
                    i++;
                } finally {
                    writeLock.unlock();
                }
                if(redisUtil != null){
                    redisUtil.sAddex(RECODE+i,"无用值");
                    redisUtil.expire(RECODE+i,60*60*2+60*5);
                }
                synBySet(set);
            }
        },new Date(),1000*60*60*2);
    }

    public static void recode(Integer aid){
        try{
            readLock.lock();
            redisUtil.zAdd(RECODE+i,aid);
        } finally {
            readLock.unlock();
        }
    }
//浏览每天算一次
    public static void addView(Integer aid,String username){
        Date date = new Date();
        String dh = sd.format(date);
        String val = username+dh;
        redisUtil.pfAdd(aid.toString(),val);
        redisUtil.expire(aid.toString(),60*60*24);
        try{
            readLock.lock();
            redisUtil.zAdd(RECODE+i,aid);
        }finally {
            readLock.unlock();
        }
    }

    public static void addLike(Integer aid,String username){
        redisUtil.zAdd(aid.toString(),username);
        redisUtil.expire(aid.toString(),60*60*24);
        try{
            readLock.lock();
            redisUtil.zAdd(RECODE+i,aid);
        }finally {
            readLock.unlock();
        }
    }

    //操作set进行同步
    private static void synBySet(Set<Long> set){
        int len = set.size();
        List<Article> list = new ArrayList<>(len);
        int i = 0;
        for(Long aid: set){
            Long likeSize = redisUtil.zSize(aid.toString());
            Long viewSize = redisUtil.pfSize(aid.toString());
            Article article = new Article();
            article.setId(aid);
            if(likeSize != null){
                article.setLikeCounts(likeSize.intValue());
            }
            if(viewSize != null){
                article.setViewCounts(viewSize.intValue());
            }
            list.add(article);
            i++;
            if(i % 500 == 0 || i == len){
                articleService.updateMany(list);
            }
        }
    }

    public static void setRedisUtil(RedisUtil redisUtil) {
        StatisticsUtil.redisUtil = redisUtil;
    }

    public static void setArticleService(ArticleService articleService) {
        StatisticsUtil.articleService = articleService;
    }
}
